package net.mimai.vultrue.bus.utils;

import net.mimai.vultrue.framework.utils.JudgeUtils;
import com.jcraft.jsch.*;
import net.mimai.vultrue.framework.utils.ReflectionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.FileInputStream;
import java.util.*;

/**
 * @ClassName SftpUtil
 * @Description TODO
 * @Author 方丈
 * @Date 2021/9/15 9:45
 * @Version 1.0.0
 **/
public class SftpUtil {
    private static Logger logger = LoggerFactory.getLogger(SftpUtil.class);

    public final static String pathSeparator="/";

    /**
     * 上传文件
     * @param server       服务器信息
     * @param options      文件传输选项
     * @throws Exception
     */
    public static void upload(Server server,Options options) throws Exception {
        logger.info(server.toString());
        logger.info(options.toString());
        String[] filePath = options.getPaths();
        if(JudgeUtils.isEmpty(filePath)){
            filePath = new String[]{options.getSinglePath()};
        }

        ChannelSftp uploadChannel = null;
        try {
            uploadChannel = getChannel(server,options);
            forceCd(uploadChannel, server);
            for (String fn : filePath) {
                File file = new File(fn);
                FileInputStream fileInputStream = new FileInputStream(file);
                try{
                    uploadChannel.put(fileInputStream, file.getName());
                }finally{
                    if(JudgeUtils.isNotNull(fileInputStream)){
                        fileInputStream.close();
                    }
                }
            }
        } catch (SftpException e) {
            throw e;
        } finally {
            releaseChannel(uploadChannel);
        }
    }

    /**
     * 进入某个路径，如果路径中的文件夹不存在则创建
     *
     * @param channelSftp 已建立的sftp通道
     * @param server  服务器信息
     */
    private static void forceCd(ChannelSftp channelSftp, Server server) throws Exception {
        if (server.targetPath().startsWith("/")) {
            channelSftp.cd("/");
        }
        String[] dirs = server.targetPath().trim().split(pathSeparator);
        for (int i = 0; i < dirs.length; i++) {
            if (!JudgeUtils.isBlank(dirs[i])) {
                try {
                    channelSftp.cd(dirs[i]);
                } catch (SftpException e) {
                    channelSftp.mkdir(dirs[i]);
                    channelSftp.cd(dirs[i]);
                }
            }
        }
    }

    /**
     * @param server       服务器信息
     * @param options      文件传输选项
     * @throws Exception
     */
    public static boolean remoteExist(Server server,Options options) throws Exception {
        logger.info(server.toString());
        logger.info(options.toString());
        ChannelSftp downChannel = null;
        String fileName = options.getFileName();
        String parentPath = server.targetPath();
        try {
            downChannel = getChannel(server,options);
            downChannel.cd(parentPath);
            Vector vector = downChannel.ls(options.getSinglePath());
            Enumeration elements = vector.elements();
            while (elements.hasMoreElements()) {
                Object fileItem = elements.nextElement();
                String itemName = (String) ReflectionUtils.getFieldValue(fileItem, "filename");
                if (itemName.trim().equals(fileName.trim())) {
                    return true;
                }
//                String fileItemJson = JSON.toJSONString(fileItem);
//                JSONObject fileItemJsonObj = JSON.parseObject(fileItemJson);
//                String itemName = (String) fileItemJsonObj.get("filename");
//                if (itemName.trim().equals(fileName.trim())) {
//                    return true;
//                }
            }
        } catch (SftpException e) {
            throw e;
        } finally {
            releaseChannel(downChannel);
        }
        return false;
    }

    /**
     * 获取文件大小
     *
     * @param server       服务器信息
     * @param options      文件传输选项
     * @throws Exception
     */
    public static long getRemoteFileSize(Server server,Options options) throws Exception {
        logger.info(server.toString());
        logger.info(options.toString());
        String fileName = options.getFileName();
        String parentPath = server.targetPath();
        ChannelSftp downChannel = null;
        try {
            downChannel = getChannel(server, options);
            downChannel.cd(parentPath);

            Vector vector = downChannel.ls(fileName);
            Enumeration elements = vector.elements();
            while (elements.hasMoreElements()) {
                Object fileItem = elements.nextElement();
                String itemName = (String) ReflectionUtils.getFieldValue(fileItem, "filename");
                if (itemName.trim().equals(fileName.trim())) {
                    SftpATTRS sftpAttrs = downChannel.lstat(fileName);
                    return sftpAttrs.getSize();
                }
            }
        } catch (SftpException e) {
            throw e;
        } finally {
            releaseChannel(downChannel);
        }
        return -1;
    }

    /**
     * @param server       服务器信息
     * @param options      文件传输选项
     * @throws Exception
     */
    public static void download(Server server,Options options) throws Exception {
        logger.info(server.toString());
        logger.info(options.toString());
        ChannelSftp downChannel = null;
        try {
            downChannel = getChannel(server,options);
            downChannel.cd(server.targetPath());
            downChannel.get(options.getFileName(), options.getLocalSavePath());
        } catch (SftpException e) {
            throw e;
        } finally {
            releaseChannel(downChannel);
        }

    }

    /**
     * @param server       服务器信息
     * @param options      文件传输选项
     * @throws Exception
     */
    public static List<String> remoteMatchRegx(Server server,Options options) throws Exception {
        logger.info(server.toString());
        logger.info(options.toString());
        ChannelSftp downChannel = null;
        List<String> fileNameList = new ArrayList<>(10);
        try {
            downChannel = getChannel(server,options);
            Vector ls = downChannel.ls(server.targetPath());
            Iterator iterator = ls.iterator();
            while (iterator.hasNext()) {
                ChannelSftp.LsEntry next = (ChannelSftp.LsEntry) iterator.next();
                if (next.getFilename().matches(options.getRegex())) {
                    String remoteFileName = next.getFilename();
                    logger.info("匹配到文件名 {}", remoteFileName);
                    fileNameList.add(remoteFileName);
                }
            }
        } catch (SftpException e) {
            throw e;
        } finally {
            releaseChannel(downChannel);
        }
        return fileNameList;
    }


    private static ChannelSftp getChannel(Server server,Options options) throws Exception {
        JSch jsch = new JSch();
        Channel channel = null;
        try {
            int port = JudgeUtils.isNull(server.getPort())?22:server.getPort();
            Session sshSession = jsch.getSession(server.getUsername(), server.getIp(), port);
            if (JudgeUtils.isBlank(server.getLoginKey())) {
                sshSession.setPassword(server.getPwd());
            } else {
                jsch.addIdentity(server.getLoginKey());
            }

            Properties sshConfig = new Properties();
            sshConfig.put("StrictHostKeyChecking", "no");
            sshSession.setConfig(sshConfig);
            sshSession.connect(options.getConnectTimeOut());
            channel = sshSession.openChannel("sftp");
            channel.connect();
        } catch (JSchException e) {
            logger.error("获取sftp通道失败", e);
            throw e;
        }
        return (ChannelSftp) channel;
    }

    private static void releaseChannel(ChannelSftp channel) {
        if (null != channel) {
            if (channel.isConnected()) {
                Session session = null;
                try {
                    session = channel.getSession();
                } catch (JSchException e) {
                    logger.error(e.getMessage());
                }

                channel.disconnect();

                if (session != null && session.isConnected()) {
                    session.disconnect();
                }

                logger.info("关闭sftp通道");
            }
        }
    }



    public static class Server {
        private String ip;

        private Integer port;

        private String username;

        private String pwd;

        /** 密钥或口令*/
        private String loginKey;

        //服务器的工作目录
        private String baseDir;

        //相对baseDir的路径
        private String path;

        public String getIp() {
            return ip;
        }

        public void setIp(String ip) {
            this.ip = ip;
        }

        public Integer getPort() {
            return port;
        }

        public void setPort(Integer port) {
            this.port = port;
        }

        public String getUsername() {
            return username;
        }

        public void setUsername(String username) {
            this.username = username;
        }

        public String getPwd() {
            return pwd;
        }

        public void setPwd(String pwd) {
            this.pwd = pwd;
        }

        public String getPath() {
            return path;
        }

        public void setPath(String path) {
            this.path = path;
        }

        public String getBaseDir() {
            return baseDir;
        }

        public void setBaseDir(String baseDir) {
            this.baseDir = baseDir;
        }

        public String getLoginKey() {
            return loginKey;
        }

        public void setLoginKey(String loginKey) {
            this.loginKey = loginKey;
        }


        public String targetPath(){
            if(!baseDir.endsWith(pathSeparator)){
                baseDir = baseDir.concat(pathSeparator);
            }
            return baseDir.concat(path);
        }

        @Override
        public String toString() {
            return "Server{" +
                    "ip='" + ip + '\'' +
                    ", port=" + port +
                    ", username='" + username + '\'' +
                    ", pwd='" + pwd + '\'' +
                    ", loginKey='" + loginKey + '\'' +
                    ", baseDir='" + baseDir + '\'' +
                    ", path='" + path + '\'' +
                    '}';
        }
    }

    public static class Options {
        int connectTimeOut;

        //上传文件的路径,多个文件时使用
        String[] paths;

        //上传文件的路径,单个文件时使用
        String singlePath;


        //正则表达式，用于匹配文件名
        String regex;

        //服务器上的文件名
        String fileName;

        //下载保存路径
        String localSavePath;

        public int getConnectTimeOut() {
            return connectTimeOut;
        }

        public void setConnectTimeOut(int connectTimeOut) {
            this.connectTimeOut = connectTimeOut;
        }

        public String getSinglePath() {
            return singlePath;
        }

        public void setSinglePath(String singlePath) {
            this.singlePath = singlePath;
        }

        public String[] getPaths() {
            return paths;
        }

        public void setPaths(String[] paths) {
            this.paths = paths;
        }

        public String getLocalSavePath() {
            return localSavePath;
        }

        public void setLocalSavePath(String localSavePath) {
            this.localSavePath = localSavePath;
        }

        public String getRegex() {
            return regex;
        }

        public void setRegex(String regex) {
            this.regex = regex;
        }

        public String getFileName() {
            return fileName;
        }

        public void setFileName(String fileName) {
            this.fileName = fileName;
        }

        @Override
        public String toString() {
            return "Options{" +
                    "connectTimeOut=" + connectTimeOut +
                    ", paths=" + Arrays.toString(paths) +
                    ", singlePath='" + singlePath + '\'' +
                    ", regex='" + regex + '\'' +
                    ", fileName='" + fileName + '\'' +
                    ", localSavePath='" + localSavePath + '\'' +
                    '}';
        }
    }
}
